from simpy import Environment

from task_service.task_context import EquInfo
from util.dateutil import ts_str, ts_date
from x_pc.core import System
from x_pc.core.carry.carry_102_csc import CSC
from x_pc.core.carry.carry_103_tsj import TSJ
from x_pc.core.carry.carry_104_hctsj import HCTSJ
from x_pc.core.system_context import SysDataCtx
from x_pc.core.unit.unit_203_jxsx import JXSX
from x_pc.core.unit.unit_204_jxcj import JXCJ
from x_pc.db.sf_data import LocDistance
from x_pc.exceptions import PcException
from x_pc.helpers import can_work_max_time
from x_pc.model.model import TaskCtx, Direction, Loc, Task, TaskStatus

# 空转等待任务时间(秒)
NO_TIME = 0.000001


class CSCSystem(CSC, System):
    """
    穿梭车系统
    高效使用【穿梭车】，减少使用【换层提升机】
    """
    # 换层提升机
    hctsj: HCTSJ = None
    # 件箱层接驳口 (左、右 两侧)
    jxcj_l_list: list[JXCJ] = []
    jxcj_r_list: list[JXCJ] = []

    # 系统数据上下文
    sys_data: SysDataCtx = None

    # 层任务
    layer_task: dict[int, list[TaskCtx]] = {}

    def __init__(self, env: Environment, name: str, hctsj: HCTSJ, jxcj_l_list: list[JXCJ], jxcj_r_list: list[JXCJ],
                 sys_data: SysDataCtx, kwargs):
        self.hctsj = hctsj
        self.jxcj_l_list = jxcj_l_list
        self.jxcj_r_list = jxcj_r_list
        self.sys_data = sys_data
        self.layer_task = dict()
        super().__init__(env, name, kwargs)
        #
        self.__init_layer()
        #
        self.times = 0
        #
        if not self.hctsj:
            msg = f'[PC] {self.name} HCTSJ 换层提升机 设备不可用'
            self.sys_data.logger.error(msg)

    def __init_layer(self):
        # 初始化CSC层
        uk = EquInfo.gen_uk(self.equ_type, self.equ_code)
        equ_info: EquInfo = self.sys_data.init_equ_dict.get(uk)
        if equ_info and equ_info.is_valid:
            equ_loc: Loc = equ_info.ext_loc
            self.layer = int(equ_loc.layer)
            msg = f'[PC] 穿梭车 {self.name} 初始化层: {self.layer}'
            self.sys_data.logger.info(msg)
        # 兜底策略
        if not self.layer:
            if self.layer_task:
                self.layer = [_ for _ in self.layer_task.keys()][0]

    def run(self):
        """
        运行
        :return:
        """
        while self.running:
            # 当 该穿梭车 无任务时。空转等待
            if not sum(self.layer_task.values(), []):
                if self.times < 5:
                    self.times += 1
                    yield self.env.timeout(NO_TIME)
                else:
                    yield self.env.timeout(1)
                continue
            task_ctx = self.choose_task()
            if task_ctx:
                yield self.env.process(self.run_task(task_ctx))
            else:
                if not self.hctsj:
                    yield self.env.timeout(100)
                    continue
                # 任务不在该层时，进行换层操作
                yield self.env.process(self.change_layer())

    def push_task(self, task_ctx: TaskCtx):
        """
        接收任务 将任务按层分组
        :param task_ctx:
        :return:
        """
        self.logger.info('[PC] PushTask -> %s, task: %s, time: %s', self.name, task_ctx.task.task_id,
                         ts_date(self.env.now))
        task: Task = task_ctx.task
        layer: int = int(task.begin_pos.layer)
        # 按层划分任务
        if layer not in self.layer_task:
            self.layer_task[layer] = []
        self.layer_task[layer].append(task_ctx)

    def choose_task(self) -> TaskCtx | None:
        """
        优先执行完本层任务，然后进行换层
        :return:
        """
        # 开始选择任务
        task_list = self.layer_task.get(self.layer)
        if task_list:
            if not self.jxcj:
                msg = f'[PC] 件箱层接驳口{self.layer}不存在，本层任务清空。taskIds: {[_.task.task_id for _ in self.layer_task[self.layer]]}'
                self.sys_data.logger.warning(msg)
                for t in task_list:
                    t.task.ext_last_info = f'件箱层接驳口{self.layer}不存在, 任务终止'
                    t.task.set_run_status(TaskStatus.ERROR, self.sys_data)
                # 将异常层任务置空，避免影响 换层策略
                self.layer_task[self.layer] = []
                # 没有件箱层接驳口，不返回任务
                return None
            # 若层接驳口 缓存未满，才返回任务
            if not self.jxcj.is_full():
                return task_list.pop(-1)
        return None

    def change_layer(self):
        """
        换层
        逻辑：
        - 最先 校验下 是否有【紧急】任务
        - 优先 校验下 是否有【长时间未执行】的任务
        - 若没有则 去任务最多的那层
            - 若有相同数量层，则选择等待较久的那层
        :return:
        """
        # TODO: 当前仅考虑 任务数量，未考虑任务长时间未被执行场景
        # 判断一下 那层任务最多
        sorted_tasks = sorted(self.layer_task.items(), key=lambda _: len(_[1]), reverse=True)
        if sorted_tasks:
            # 目标层
            target_layer = sorted_tasks[0][0]
            if target_layer != self.layer:
                # 占用穿梭车
                with self.resource.request() as req:
                    yield req
                    # 进行换层
                    with self.hctsj.resource.request() as req2:
                        yield req2
                        yield self.env.timeout(self.hctsj.cost() / self.sys_data.time_cost_ratio)
                        self.layer = target_layer
            else:
                # 若计划换层，但实际未换层 步进一下
                yield self.env.timeout(1)

    def run_task(self, task_ctx: TaskCtx):
        """
        开始执行任务，接到的任务都是本层的
        :param task_ctx:
        :return:
        """
        # 设备是否锁定判断
        task = task_ctx.task
        process = task.process
        pending_list = self.sys_data.get_pending_device(self.device_type(), self.device_code())
        if not can_work_max_time(self.env.now, 0, pending_list):
            while self.running:
                yield self.env.timeout(1)
                if can_work_max_time(self.env.now, 0, pending_list):
                    break
        #
        self.sys_data.logger.info(
            f'[PC] CSC {self.name}, task_id: {task_ctx.task.task_id}, time: {ts_str(self.env.now)}')
        with self.resource.request() as req:
            yield req
            #
            process.begin_ts = self.env.now
            process.begin_pos = process.begin_pos.raw_loc if isinstance(process.begin_pos, Loc) else process.begin_pos
            process.set_origin_pack_type(task.origin_pack_type)
            process.set_origin_sku_num(task.origin_sku_num)
            process.operate_pack_type = process.ext_work_type.operate_pack_type
            process.operate_pack_num = 1
            #
            process.device_type = self.device_type()
            process.device_code = self.device_code()
            # 穿梭车移动耗时 * 2
            loc_distance = self.sys_data.get_lane_loc(process.begin_pos)
            if not loc_distance:
                loc_cost = 999999
                self.sys_data.logger.error(
                    f'[PC] 件箱 穿梭车 耗时不存在，task: {task.task_id}, loc: {process.begin_pos}')
                task.ext_last_info = f'穿梭车耗时不存在, 任务终止. loc: {process.begin_pos}'
                task.set_run_status(TaskStatus.ERROR, self.sys_data)
            else:
                loc_cost = loc_distance.move_time
            yield self.env.timeout(float(loc_cost) / self.sys_data.time_cost_ratio * 2)
            process.end_pos = process.device_code
            process.end_ts = self.env.now
        # 前任务终点
        task.pre_process().end_pos = self.jxcj.loc()
        # ----------- 穿梭车 --> 层接驳口
        process = task.process
        process.begin_ts = self.env.now
        process.begin_pos = self.jxcj.loc()
        process.set_origin_pack_type(task.pre_process().target_pack_type)
        process.set_origin_sku_num(task.pre_process().target_sku_num)
        process.operate_pack_type = process.ext_work_type.operate_pack_type
        process.operate_pack_num = 1
        #
        process.device_type = self.jxcj.device_type()
        process.device_code = self.jxcj.device_code()
        # 必须在选择任务时保证，还有空余位置
        yield self.jxcj.store.put(TaskCtx(task))
        #
        process.end_pos = self.jxcj.loc()
        process.end_ts = self.env.now

    @property
    def jxcj(self) -> JXCJ | None:
        jxcj: JXCJ | None = None
        try:
            layer_idx = self.layer - 1
            jxcj = self.jxcj_r_list[layer_idx] if self.jxcj_r_list[layer_idx] else self.jxcj_l_list[layer_idx]
        except Exception as ex:
            self.sys_data.logger.warning('[PC] 件箱层接 出现异常')
            pass
        return jxcj


class JXLaneV2(System):
    """
    件箱 巷道 改版V2
    """
    # 穿梭车数据
    csc_list: list[CSCSystem] = []
    csc_code_dict: dict[str, CSCSystem] = {}
    csc_layer_dict: dict[int, CSCSystem] = {}
    # 提升机(左右两侧各一个)
    tsj_l: TSJ = None
    tsj_r: TSJ = None
    # 换层提升机
    hctsj: HCTSJ = None
    # 件箱上下架口
    jxsx_l: JXSX = None
    jxsx_r: JXSX = None
    # 件箱层 接驳口(左右两侧)
    jxcj_l_list: list[JXCJ] = []
    jxcj_r_list: list[JXCJ] = []

    # 系统数据上下文
    sys_data: SysDataCtx = None

    def __init__(self, env: Environment, name: str, code: str, sys_data: SysDataCtx, csc_list, tsj_list: list[TSJ],
                 hctsj, jxsx_list, jxcj_list):
        self.csc_list: list[CSCSystem] = []
        self.csc_code_dict: dict[str, CSCSystem] = {}
        self.csc_layer_dict: dict[int, CSCSystem] = {}
        self.jxcj_l_list: [JXCJ] = []
        self.jxcj_r_list: list[JXCJ] = []
        #
        super().__init__(env, name, code)
        self.sys_data = sys_data
        self.__init_hctsj(hctsj)
        self.__init_jxcj_list(jxcj_list)
        self.__init_csc_list(csc_list)
        self.__init_tsj_list(tsj_list)
        self.__init_jxsx_list(jxsx_list)
        #
        self.times = 0

    def __init_hctsj(self, hctsj):
        """
        换层提升机
        :param hctsj:
        :return:
        """
        if not hctsj:
            raise PcException(f'[PC] 件箱巷道(lane: {self.code}) 换层提升机 无数据')
        #
        hctsj = HCTSJ(self.env, '换层提升机', hctsj)
        if self.sys_data.is_valid_equ(hctsj):
            self.hctsj = hctsj
        else:
            self.sys_data.logger.warning('[PC] 件箱巷道(lane: %s) 换层提升机 不可用', self.code)

    def __init_jxcj_list(self, jxcj_list):
        """
        件箱层接驳口
        :param jxcj_list:
        :return:
        """
        if not jxcj_list:
            raise PcException(f'[PC] 件箱巷道(lane: {self.code}) 件箱层接驳口 无数据')
        # 件箱层 接驳口 左侧
        jxcj_left_list = sorted(
            [_ for _ in jxcj_list if _[0][SysDataCtx.LEFT_RIGHT_DB_KEY] == str(Direction.LEFT.value)],
            key=lambda _: _[0][SysDataCtx.LAYER_DB_KEY])
        for jxcj in jxcj_left_list:
            if jxcj[1]:
                device = JXCJ(self.env, f'{jxcj[0][SysDataCtx.LAYER_DB_KEY]}层-左侧-接驳口',
                              int(jxcj[0][SysDataCtx.LAYER_DB_KEY]), jxcj[1])
                if self.sys_data.is_valid_equ(device):
                    self.jxcj_l_list.append(device)
                    continue
            self.jxcj_l_list.append(None)

        # 件箱层 接驳口 右侧
        jxcj_right_list = sorted(
            [_ for _ in jxcj_list if _[0][SysDataCtx.LEFT_RIGHT_DB_KEY] == str(Direction.RIGHT.value)],
            key=lambda _: _[0][SysDataCtx.LAYER_DB_KEY])
        for jxcj in jxcj_right_list:
            if jxcj[1]:
                device = JXCJ(self.env, f'{jxcj[0][SysDataCtx.LAYER_DB_KEY]}层-右侧-接驳口',
                              int(jxcj[0][SysDataCtx.LAYER_DB_KEY]), jxcj[1])
                if self.sys_data.is_valid_equ(device):
                    self.jxcj_r_list.append(device)
                    continue
            self.jxcj_r_list.append(None)

    def __init_csc_list(self, csc_list):
        """
        穿梭车
        :param csc_list:
        :return:
        """
        if not csc_list:
            raise PcException(f'[PC] 件箱巷道(lane: {self.code}) 穿梭车数据 无数据')
        #
        sorted_csc_list = sorted(csc_list, key=lambda _: _[0]['layer'])
        for idx, csc_t in enumerate(sorted_csc_list):
            csc_d = csc_t[1]
            csc_code = csc_d[SysDataCtx.DEVICE_CODE_DB_KEY]
            if csc_code not in self.csc_code_dict:
                device = CSCSystem(self.env, f'[{self.code}]穿梭车-{csc_code}', self.hctsj, self.jxcj_l_list,
                                   self.jxcj_r_list,
                                   self.sys_data, csc_d)
                if not self.sys_data.is_valid_equ(device):
                    continue
                self.csc_code_dict[csc_code] = device
                self.csc_list.append(self.csc_code_dict[csc_code])
            self.csc_layer_dict[idx + 1] = self.csc_code_dict[csc_code]

    def __init_tsj_list(self, tsj_list):
        """
        提升机
        :param tsj_list:
        :return:
        """
        if not len(tsj_list) == 2:
            raise PcException(f'[PC] 件箱巷道(lane: {self.code}) 提升机数据异常, tsj_list: {len(tsj_list)}')
        #
        tsj_r, tsj_l = None, None
        for tsj_t in tsj_list:
            if tsj_t[0][SysDataCtx.LEFT_RIGHT_DB_KEY] == str(Direction.RIGHT.value):
                tsj_r = TSJ(self.env, '提升机-右', tsj_t[1])
            if tsj_t[0][SysDataCtx.LEFT_RIGHT_DB_KEY] == str(Direction.LEFT.value):
                tsj_l = TSJ(self.env, '提升机-左', tsj_t[1])
        # assert self.tsj_r, f'件箱巷道(lane: {self.code}) 提升机-右 无数据 {self.tsj_r}'
        # assert self.tsj_l, f'件箱巷道(lane: {self.code}) 提升机-左 无数据 {self.tsj_l}'
        if self.sys_data.is_valid_equ(tsj_r):
            self.tsj_r = tsj_r
        if self.sys_data.is_valid_equ(tsj_l):
            self.tsj_l = tsj_l

    def __init_jxsx_list(self, jxsx_list):
        """
        件箱上下架口
        :param jxsx_list:
        :return:
        """
        if not jxsx_list:
            raise PcException(f'[PC] 件箱巷道(lane: {self.code}) 件箱上下口 无数据')
        # 件箱巷道 上下架口 左侧
        jxsx_left_list = [_[1] for _ in jxsx_list if _[0][SysDataCtx.LEFT_RIGHT_DB_KEY] == str(Direction.LEFT.value)]
        jxsx_left_list.sort(key=lambda _: _[SysDataCtx.INOUT_MODE_DB_KEY])
        if not jxsx_left_list:
            raise PcException(f'[PC] 件箱巷道(lane: {self.code}) 件箱上下口-左 无数据')
        jxsx_l = JXSX(self.env, f'件箱上下口-左', jxsx_left_list[1])
        #
        if self.sys_data.is_valid_equ(jxsx_l):
            self.jxsx_l = jxsx_l
        else:
            self.sys_data.logger.warning(
                f'[PC] 设备不可用: {self.name}, {jxsx_l.name} 不可用。设备type: {jxsx_l.equ_type}, 设备code: {jxsx_l.equ_code}')

        # 件箱巷道 上下架口 右侧
        jxsx_right_list = [_[1] for _ in jxsx_list if _[0][SysDataCtx.LEFT_RIGHT_DB_KEY] == str(Direction.RIGHT.value)]
        jxsx_right_list.sort(key=lambda _: _[SysDataCtx.INOUT_MODE_DB_KEY])
        if not jxsx_right_list:
            raise PcException(f'[PC] 件箱巷道(lane: {self.code}) 件箱上下口-右 无数据')
        jxsx_r = JXSX(self.env, f'件箱上下口-右', jxsx_right_list[1])
        #
        if self.sys_data.is_valid_equ(jxsx_r):
            self.jxsx_r = jxsx_r
        else:
            self.sys_data.logger.warning(
                f'[PC] 设备不可用: {self.name}, {jxsx_r.name} 不可用。设备type: {jxsx_r.equ_type}, 设备code: {jxsx_r.equ_code}')

    def run(self):
        """
        运行
        :return:
        """
        while self.running:
            if not self.task_list:
                if self.times < 5:
                    self.times += 1
                    yield self.env.timeout(NO_TIME)
                else:
                    yield self.env.timeout(1)
                continue
            task_ctx = self.choose_task()
            self.run_task(task_ctx)
            yield self.env.timeout(0 / self.sys_data.time_cost_ratio)

    def run_task(self, task_ctx: TaskCtx):
        """
        执行任务
        将任务分配至相应的 穿梭车上面
        """
        task = task_ctx.task
        layer = task.begin_pos.layer
        layer_int = int(layer)
        # 穿梭车
        csc: CSCSystem = self.csc_layer_dict.get(layer_int)
        if not csc:
            msg = f'[PC] 件箱 task: {task.task_id}, 巷道: {task.begin_pos.lane}, 层: {layer_int} CSC 穿梭车 不可用'
            self.sys_data.logger.error(msg)
            task.ext_last_info = f'穿梭车不可用, 任务终止. lane: {task.begin_pos.lane}, layer: {layer_int}, loc: {task.begin_pos}'
            task.set_run_status(TaskStatus.ERROR, self.sys_data)
            return
        csc.push_task(task_ctx)

    def tsj_run(self):
        """
        提升机运行
        :return:
        """
        self.env.process(self.__tsj_run(self.tsj_l, self.jxcj_l_list, self.jxsx_l, '左侧'))
        self.env.process(self.__tsj_run(self.tsj_r, self.jxcj_r_list, self.jxsx_r, '右侧'))

    def __tsj_run(self, tsj: TSJ, jxcj_list: list[JXCJ], jxsx: JXSX, remark: str):
        """
        提升机 取货物规则
        TODO: 这块提升机可以选择 取货规则， 默认 由上至下 进行取货
        :param tsj: 提升机
        :param jxcj_list: 件箱层接驳口
        :param jxsx: 件箱上下口
        :return:
        """
        # TODO: 从件箱 层接口中  取出货物、放置到自身缓存中，再将货物运送至 件箱上下接驳口
        while self.running:
            # 若没有提升机，则略过
            if not tsj:
                msg = f'[PC] 件箱巷道: {self.name} 提升机 不存在'
                self.sys_data.logger.error(msg)
                break
            if not jxsx:
                msg = f'[PC] 件箱巷道: {self.name} 件箱上下架口 不存在'
                self.sys_data.logger.error(msg)
                break
            for jxcj in reversed([_ for _ in jxcj_list if _]):
                if tsj.is_full():
                    break
                while not jxcj.is_empty():
                    # 层接驳口 取物， 提升机 放物
                    task_ctx: TaskCtx = yield jxcj.store.get()
                    # 初始 信息记录
                    task = task_ctx.task
                    process = task.process
                    process.begin_ts = self.env.now
                    process.begin_pos = jxcj.loc()
                    process.set_origin_pack_type(task.pre_process().target_pack_type)
                    process.set_origin_sku_num(task.pre_process().target_sku_num)
                    process.operate_pack_type = process.ext_work_type.operate_pack_type
                    process.operate_pack_num = 1
                    #
                    process.device_type = tsj.device_type()
                    process.device_code = tsj.device_code()
                    yield tsj.store.put(TaskCtx(task_ctx.task, jxcj=jxcj))
                    if tsj.is_full():
                        break
            if tsj.is_empty():
                yield self.env.timeout(1)
                continue
            # 耗时计算 按最长耗时为准
            max_lift_time = max(
                [float(self.sys_data.get_lane_loc(_.task.begin_pos).lift_time) for _ in tsj.store.items])
            yield self.env.timeout(max_lift_time / self.sys_data.time_cost_ratio)
            # 当 提升机不为空时，需要卸货
            if not tsj.is_empty():
                # 将 提升机的货物移至 出入口
                while not tsj.is_empty():
                    # 当出口满时 等待
                    if jxsx.is_full():
                        yield self.env.timeout(1)
                        continue
                    task_ctx: TaskCtx = yield tsj.store.get()
                    task_ctx.jxsx = jxsx
                    # 终止 信息记录
                    task = task_ctx.task
                    process = task.process
                    process.end_pos = jxsx.loc()
                    process.end_ts = self.env.now
                    # 件箱上下口
                    process = task.process
                    process.begin_ts = self.env.now
                    process.begin_pos = jxsx.loc()
                    process.set_origin_pack_type(task.origin_pack_type)
                    process.set_origin_sku_num(task.origin_sku_num)
                    process.operate_pack_type = process.ext_work_type.operate_pack_type
                    process.operate_pack_num = 1
                    #
                    process.device_type = jxsx.device_type()
                    process.device_code = jxsx.device_code()
                    process.end_pos = jxsx.loc()
                    process.end_ts = self.env.now
                    # 件箱上下架口，下一步  由轨道接管
                    yield jxsx.store.put(task_ctx)
                # 提升机 搬运完货物后，扫描到有货物后，即刻 推进模拟器产生耗时
                # 循环判断 所有件箱层接 是否有货物，有则运动到最高有货物的层
                lift_time: float = -1
                while True:
                    for jxcj in reversed([_ for _ in jxcj_list if _]):
                        if not jxcj.is_empty():
                            # 获取移动至该层的耗时
                            task_ctx: TaskCtx = jxcj.store.items[0]
                            loc = task_ctx.task.begin_pos
                            loc_distance: LocDistance = self.sys_data.get_lane_loc(loc)
                            if loc_distance:
                                lift_time: float = float(loc_distance.lift_time)
                            else:
                                lift_time = 5.0
                            break
                    if lift_time >= 0:
                        break
                    else:
                        yield self.env.timeout(1)
                yield self.env.timeout(lift_time / self.sys_data.time_cost_ratio)

    def start(self):
        super().start()
        self.tsj_run()
        for csc in self.csc_list:
            csc.start()

    def stop(self):
        for csc in self.csc_list:
            csc.stop()
        super().stop()

    def __str__(self):
        return f'{self.__class__.__name__}(code={self.code})'


class SystemJX(System):
    """
    件箱巷道 系统
    """
    # 巷道 列表/字典
    lane_list: list[JXLaneV2] = []
    lane_dict: dict[str, JXLaneV2] = {}
    sys_data: SysDataCtx = None

    def __init__(self, env: Environment, name: str, sys_data: SysDataCtx):
        self.lane_list: list[JXLaneV2] = []
        self.lane_dict: dict[str, JXLaneV2] = {}
        #
        self.sys_data = sys_data
        super().__init__(env, name)
        self.__init_lane_list()

    def __init_lane_list(self):
        """
        初始化 件箱 巷道
        :return:
        """
        jx_lane_data = self.sys_data.get_jx_lane_data()
        for lane in jx_lane_data:
            lane_data = jx_lane_data[lane]
            csc_list = lane_data.get(str(CSC.device_type()))
            tsj_list = lane_data.get(str(TSJ.device_type()))
            jxsx_list = lane_data.get(str(JXSX.device_type()))
            jxcj_list = lane_data.get(str(JXCJ.device_type()))
            hctsj = self.sys_data.get_hctsj()
            jx = JXLaneV2(self.env, f'件箱巷道-{lane}', lane, self.sys_data, csc_list, tsj_list, hctsj, jxsx_list,
                          jxcj_list)
            self.lane_list.append(jx)
            self.lane_dict[lane] = jx

    def run_task(self, task_ctx: TaskCtx):
        pos = task_ctx.task.begin_pos
        lane_code: str = pos.lane
        if lane_code in self.lane_list:
            self.sys_data.logger.error(
                f'[PC] 任务数据异常 巷道起始LOC异常。task: {task_ctx.task.task_id}, 起始POS: {pos}, '
                f'巷道编码: {lane_code}, 已知巷道数据: {self.lane_dict.keys()}')
            return  # 分配到指定巷道执行
        lane: JXLaneV2 = self.lane_dict.get(lane_code)
        lane.push_task(task_ctx)
        # 必须是生成器
        self.sys_data.logger.info(
            f'[PC] JX task: {task_ctx.task.task_id}, lane: {lane.name}, time: {ts_str(self.env.now)}')
        yield self.env.timeout(0)

    def __str__(self):
        return f'{self.__class__.__name__} ({self.name})'

    def start(self):
        super().start()
        # 巷道
        for lane in self.lane_list:
            lane.start()

    def stop(self):
        # 巷道
        for lane in self.lane_list:
            lane.stop()
        super().stop()
